#coding:utf-8

"""
Q Learning 算法 最决策的部分 相当与机器人的大脑
"""

import numpy as np
import pandas as pd


class QLearning:

    def __init__(self, actions, learning_rate=0.01, discount_factor=0.9, e_greedy=0.1):

        # action 列表
        self.actions = actions

        # 学习速率
        self.lr = learning_rate

        # 折扣因子
        self.gamma = discount_factor

        # 贪婪度
        self.epsilon = e_greedy
        # Q 表
        self.q_table = pd.DataFrame(columns=self.actions, dtype=np.float32)

    # 检测 q_table 中 有没有这个 state
    # 如果还没有当前 state 那我们就插入一组全 0 数据 作为这个state的所有 action 的初始值
    def check_state_exist(self, state):
        if state not in self.q_table.index:
            # 插入一组全 0 数据
            self.q_table = self.q_table.append(
                pd.Series([0] * len(self.actions), index=self.q_table.columns, name=state,)
            )

    # 根据 state 来选择 action
    def choose_action(self, state):
        # 检测次 state 是否在 q_tate 中 存在
        self.check_state_exist(state)

        # 选行为 用 Epsilon Greedy 贪婪方法
        if np.random.uniform() < self.epsilon:
            # 随机选择 action
            action = np.random.choice(self.actions)
        else:
            # 选择 Q 值 最高的 action
            state_action = self.q_table.loc[state, :]
            # 同一个 state 可能会有多个相同的 Q action值  所以我们去乱序 一下
            state_action = state_action.reindex(np.random.permutation(state_action.index))
            action = state_action.idxmax()
        return action

    # 学习  更新 Q 表中的值
    def learn(self, s, a, r, s_):
        # 检测q_table 中 是否存在 s_
        self.check_state_exist(s_)
        # 根据Q 表的到的估计(predict) 值
        q_predict = self.q_table.loc[s, a]

        # q_target是现实 值
        if s_ != "terminal":
            # 下个 state 不是终止符
            q_target = r + self.gamma * self.q_table.loc[s_, :].max()
        else:
            # 下一个 state 是终止符
            q_target = r

        # 跟新 Q 表中 state-action 值
        self.q_table.loc[s, a] += self.lr * (q_target - q_predict)
